seguridad_en_aplicaciones_webfandomcom_es-20200213-history
Inyección SQL
Introducción Inyección SQL es una técnica donde un atacante crea o altera comandos SQL existentes para exponer datos ocultos, sobreponerse a los que son importantes, o peor aún, ejecutar comandos peligrosos a nivel de sistema en el equipo donde se encuentra la base de datos. Esto se logra a través de la aplicación, tomando la entrada del usuario y combinándola con parámetros estáticos para elaborar una consulta SQL. El origen de la vulnerabilidad radica en el incorrecto chequeo y/o filtrado de las variables utilizadas en un programa que contiene, o bien genera, código SQL. Es, de hecho, un error de una clase más general de vulnerabilidades que puede ocurrir en cualquier lenguaje de programación o script que esté embebido dentro de otro. Se dice que existe o se produjo una inyección SQL cuando, de alguna manera, se inserta o "inyecta" código SQL invasor dentro del código SQL programado, a fin de alterar el funcionamiento normal del programa y lograr así que se ejecute la porción de código "invasor" incrustado, en la base de datos. Este tipo de intrusión normalmente es de carácter malicioso, dañino o espía, por tanto es un problema de seguridad informática, y debe ser tomado en cuenta por el programador de la aplicación para poder prevenirlo. Un programa elaborado con descuido, displicencia o con ignorancia del problema, podrá resultar ser vulnerable, y la seguridad del sistema (base de datos) podrá quedar eventualmente comprometida. La intrusión ocurre durante la ejecución del programa vulnerable, ya sea, en computadores de escritorio o bien en sitios Web, en éste último caso obviamente ejecutándose en el servidor que los aloja. La vulnerabilidad se puede producir automáticamente cuando un programa "arma descuidadamente" una sentencia SQL en tiempo de ejecución, o bien durante la fase de desarrollo, cuando el programador explicita la sentencia SQL a ejecutar en forma desprotegida. En cualquier caso, siempre que el programador necesite y haga uso de parámetros a ingresar por parte del usuario, a efectos de consultar una base de datos; ya que, justamente, dentro de los parámetros es donde se puede incorporar el código SQL intruso. Al ejecutarse la consulta en la base de datos, el código SQL inyectado también se ejecutará y podría hacer un sinnúmero de cosas, como insertar registros, modificar o eliminar datos, autorizar accesos e, incluso, ejecutar otro tipo de código malicioso en el computador. Ejemplos Debido a la falta de validación en la entrada de datos y conectándose a la base de datos con privilegios de super usuario o de alguien con privilegios que puede crear usuarios, el atacante podría crear un super usuario en su base de datos. Ejemplo #1 Dividiendo el conjunto de resultados en páginas ... y haciendo super usuarios (PostgreSQL) $offset = $argv0; // Cuidado, no hay validación en la entrada de datos! $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; $result = pg_query($conn, $query); ?> Los usuarios normales dan clic en los enlaces 'siguiente' o 'atras' donde está codificado en la . El script espera que el entrante sea un número décimal. Sin embargo, qué pasa si alguien intenta irrumpir añadiendo una función urlencode() al formulario de la siguiente 0; insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) select 'crack', usesysid, 't','t','crack' from pg_shadow where usename='postgres'; -- Si esto sucedió, entonces el script podría presentarle un acceso de super usuario al atacante. Nótese que 0; es para proveer un offset válido a la consulta original y para finalizarla. Una forma factible de obtener contraseñas es burlar las páginas de búsqueda de resultados. Lo único que el atacante necesita hacer es ver si hay variables que hayan sido enviadas y sean usadas en declaraciones SQL las cuales no sean manejadas apropiadamente. Esos filtros pueden ser puestos comunmente en un formulario anterior para personalizar las cláusulas WHERE, ORDER BY, LIMIT y OFFSET en las declaraciones SELECT. Si su base de datos soporta el constructor UNION, el atacante podría intentar añadir una consulta enetera a la consulta original para listar contraseñas de una tabla arbitraria. Utilizar campos de contraseña encriptadoslds es fuertemente recomendado. Ejemplo #2 Listando nuestros artículos ... y algunas contraseñas (de cualquier servidor de base de datos) $query = "SELECT id, name, inserted, size FROM products WHERE size = '$size'"; $result = odbc_exec($conn, $query); ?> La parte estática de la consulta puede ser combinada con otra declaración SELECT la cual revela todas las contraseñas: ' union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; -- Si esta consulta (ejecutándose con ' y --) fuera asignada a una de las variables utilizadas en , la consulta reaccionará bestialmente. Las consultas de actualización de SQL, también son susceptibles a ataques. Estas consultas también son amenazadas por acortamiento y adición en una consulta completamente nueva a esta. Sin embargo el atacante podría manipularla con la cláusula SET. En este caso, algunos esquemas de información deben ser procesados para manipular la consulta exitosamente. Este puede adquirirse examinando la forma de nombres de las variables, o simplemente forzarlo con un ataque de fuerza bruta. No hay muchas convenciones de nombres para campos que almacenan contraseñas o nombres de usuarios. Ejemplo #3 Desde re-establecer una contraseña ... hasta ganar más privilegios (en cualquier servidor de bases de datos) $query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';"; ?> Pero un usuario malicioso podría enviar el valor ' or uid like'%admin% a para cambiar la contraseña del administrador, o simplemente cambiar a hehehe', trusted=100, admin='yes para obtener más privilegios. Entonces, la consulta sería cambiada: // $uid: ' or uid like '%admin% $query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';"; // $pwd: hehehe', trusted=100, admin='yes $query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE ...;"; ?> Un ejemplo horrible de cómo pueden ser accedidos los comandos a nivel de sistema operativo en algunos hospedadores de bases de datos. Ejemplo #4 Atacando el sistema operativo que hospeda la base de datos (Servidor MSSQL) $query = "SELECT * FROM products WHERE id LIKE '%$prod%'"; $result = mssql_query($query); ?> Si un atacante envía el valor a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- hacia , la consulta será: $query = "SELECT * FROM products WHERE id LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD' --%'"; $result = mssql_query($query); ?> El servidor MSSQL ejecuta la sentencia SQL en el lote que incluye un comando para añadir un usuario nuevo a la base de datos de cuentas locales. Si esta aplicación estuviera ejecutándose como sa, y el servicio MSSQLSERVER se está ejecutando con los privilegios suficientes, el atacante ahora podría tener una cuenta con la cual tendría acceso a esta máquina. Evitar la inyección SQL El principal problema de estos ataques es que si dejamos que el usuario del programa introduzca libremente caracteres sin control ninguno (mediante formularios, por ejemplo) puede llegar a aprovecharse de las comillas (simples y dobles con las que declaramos cadenas de texto o strings). Por lo tanto la solución genérica sería evitar que se pudieran introducir caracteres especiales (como comillas) sin haberlas transformado antes (por ejemplo, una comilla doble: " debería de transformarse en \" que así interpretará como texto la comilla y no como el carácter que cierra o abre el una texto en la consulta, pero según el lenguaje se puede implementar de distintas formas y algunas son automáticas y más optimizadas. Independientemente del lenguaje, ya que cualquier lenguaje que use bases de datos SQL podría ser victima de estos ataques. Otra forma de ayudar a prevenir estos tipos de ataques es evitar la utilización de nombres de tablas genéricas ( Tabla_Usuarios, Tabla_Contraseña, etc.). A continuación se mostraran distintas formas de evitar la inyección SQL en diversos lenguajes de programación: PHP En PHP tenemos varias formas de hacerlo, entre ellas para bases de datos MySQL tenemos la funciónmysql_real_escape_string, que añadiremos en las variables que introducimos en la consulta. $respuesta=mysql_query("SELECT * FROM `Usuarios` WHERE `user`='".mysql_real_escape_string($name)."' AND `pass`='".mysql_real_escape_string($password)."'") .NET En .NET evitaremos la inyección en SQL Server (con C#) estableciendo el tipo de parámetro como literal con SqlDbType.VarChar. SqlConnection con = new SqlConnection(_connectionString); SqlCommand cmd = new SqlCommand("SELECT * FROM Usuarios WHERE user=@user AND pass=@pass", con); /* Convertimos en literal estos parámetros, por lo que no podrán hacer la inyección */ cmd.Parameters.Add("@user", SqlDbType.VarChar, 32).Value = user; cmd.Parameters.Add("@pass", SqlDbType.VarChar, 64).Value = password; O también podríamos usar AddWithValue de una forma ligeramente similar a la anterior: using( SqlConnection con = (acquire connection) ) { con. Open(); using( SqlCommand cmd = new SqlCommand("SELECT * FROM Usuarios WHERE user=@user AND pass=@pass", con) ) { /* Convertimos también en literales los parámetros */ cmd.Parameters.AddWithValue("@user", user); cmd.Parameters.AddWithValue("@pass", password); using( SqlDataReader rdr = cmd.ExecuteReader() ){ /* ... */ } } } Java Ahora con Java lo que hacemos es similar a lo que hemos hecho con C# (en .NET), crear la consulta con los parámetros y posteriormente establecerlos (sustituirlos siendo ya estos un literal, que no producirán fallos de seguridad). Connection con = (acquire Connection) PreparedStatement query = con.prepareStatement("SELECT * FROM Usuarios WHERE user=? AND pass=?"); query.setString(1, user); query.setString(2, password); ResultSet rset = query.executeQuery(); Perl En este ejemplo de Perl usamos la característica placeholder que al fin y al cabo es igual que con los dos ejemplos anteriores, agregando a la consulta los parámetros (con este método se pone a punto las comillas para evitar las inyecciones SQL). $query = $sql->prepare("SELECT * FROM Usuarios WHERE user=? AND pass=?"); $query->execute($user,$password);